use core::ffi;
use std::marker::PhantomData;
#[cfg(unix)]
use std::mem::{size_of, MaybeUninit};

use ndk::android::looper::*;

pub trait LooperHandler {
	fn on(&self, _fd: ffi::c_int) {}
}

#[repr(transparent)]
#[derive(Debug, PartialEq)]
pub struct Looper {
	inner: *const ALooper,
}
impl Clone for Looper {
	fn clone(&self) -> Self {
		unsafe { ALooper_acquire(self.inner as _) };
		Self { inner: self.inner }
	}
}
impl Drop for Looper {
	fn drop(&mut self) {
		unsafe { ALooper_release(self.inner as _) }
	}
}
impl Looper {
	pub fn from_raw(inner: *mut ALooper) -> Option<Self> {
		if !inner.is_null() {
			Some(Self { inner })
		} else {
			None
		}
	}
	pub fn current() -> Option<Self> {
		let inner = unsafe { ALooper_forThread() };
		Self::from_raw(inner)
	}
	pub fn prepare(opts: ffi::c_int) -> Option<Self> {
		let inner = unsafe { ALooper_prepare(opts) };
		Self::from_raw(inner)
	}
	pub fn add<T: LooperHandler>(&self, fd: ffi::c_int, data: &std::rc::Rc<T>) -> Result<LooperGuard<T>, i32> {
		let weak = std::rc::Rc::downgrade(&data);
		let data = weak.into_raw();
		let ret = unsafe {
			ALooper_addFd(
				self.inner as _,
				fd,
				ALOOPER_POLL_CALLBACK,
				ALOOPER_EVENT_INPUT as _,
				Some(on_event::<T>),
				data as _,
			)
		};
		if ret == 1 {
			Ok(LooperGuard {
				fd,
				data,
				looper: self.clone(),
				_t: PhantomData,
			})
		} else {
			Err(ret)
		}
	}
	fn del<T>(&self, fd: ffi::c_int, data: *const T) -> Result<(), i32> {
		let ret = unsafe { ALooper_removeFd(self.inner as _, fd) };
		let _ = unsafe { std::rc::Weak::from_raw(data) };
		if ret == 1 {
			Ok(())
		} else {
			Err(ret)
		}
	}
}
extern "C" fn on_event<T: LooperHandler>(fd: ffi::c_int, _events: ffi::c_int, data: *mut ffi::c_void) -> ffi::c_int {
	let weak = unsafe { std::rc::Weak::from_raw(data as *const T) };
	if let Some(data) = weak.upgrade() {
		data.on(fd);
	}
	let _ = weak.into_raw();
	1
}

pub struct LooperGuard<T> {
	data: *const T,
	fd: ffi::c_int,
	looper: Looper,
	_t: PhantomData<T>,
}
impl<T> Drop for LooperGuard<T> {
	fn drop(&mut self) {
		self.looper.del::<T>(self.fd, self.data).ok();
	}
}

#[cfg(unix)]
pub fn pipe<T>() -> (Sender<T>, Recver<T>) {
	let mut msgpipe: [libc::c_int; 2] = [-1, -1];
	unsafe {
		if libc::pipe(msgpipe.as_mut_ptr()) != 0 {
			panic!("could not create  pipe: {}", std::io::Error::last_os_error());
		}
	}
	(
		Sender {
			fd_write: msgpipe[1],
			_t: PhantomData,
		},
		Recver {
			fd_read: msgpipe[0],
			_t: PhantomData,
		},
	)
}
#[cfg(not(unix))]
pub fn pipe<T>() -> (Sender<T>, Recver<T>) {
	unreachable!()
}

#[derive(Debug)]
pub struct Recver<T> {
	pub fd_read: ffi::c_int,
	_t: PhantomData<T>,
}
impl<T: Sized> Recver<T> {
	#[cfg(unix)]
	pub fn recv(&self) -> Result<T, i32> {
		let mut buf = MaybeUninit::<T>::uninit();
		loop {
			match unsafe { libc::read(self.fd_read, buf.as_mut_ptr() as _, size_of::<T>() as _) } {
				i if i == size_of::<T>() as _ => unsafe {
					return Ok(buf.assume_init());
				},
				-1 => {
					let err = std::io::Error::last_os_error();
					if err.kind() != std::io::ErrorKind::Interrupted {
						return Err(-1);
					}
					continue;
				}
				_ => {
					return Err(-2);
				}
			}
		}
	}
	#[cfg(not(unix))]
	pub fn recv(&self) -> Result<T, i32> {
		unreachable!()
	}
}

#[derive(Debug, Clone, Copy)]
pub struct Sender<T> {
	pub fd_write: ffi::c_int,
	pub _t: PhantomData<T>,
}
impl<T> Sender<T> {
	#[cfg(unix)]
	pub fn send(&self, cmd: T) -> Result<(), i32> {
		loop {
			match unsafe { libc::write(self.fd_write, &cmd as *const _ as *const _, size_of::<T>() as _) } {
				i if i == size_of::<T>() as _ => return Ok(()),
				-1 => {
					let err = std::io::Error::last_os_error();
					if err.kind() != std::io::ErrorKind::Interrupted {
						return Err(-1);
					}
					continue;
				}
				_ => {
					return Err(-2);
				}
			}
		}
	}
	#[cfg(not(unix))]
	pub fn send(&self, _: T) -> Result<(), i32> {
		unreachable!()
	}
}
